package com.dhy.qigsawsplitdownloader

import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.system.Os
import android.util.Log
import com.iqiyi.android.qigsaw.core.splitdownload.DownloadCallback
import com.iqiyi.android.qigsaw.core.splitdownload.DownloadRequest
import com.iqiyi.android.qigsaw.core.splitdownload.Downloader
import com.liulishuo.okdownload.DownloadTask
import com.liulishuo.okdownload.StatusUtil
import java.io.File
import kotlin.math.max
import kotlin.math.min

/**
 * @param threshold default is 10MB
 * @param progressInterval minIntervalMillisCallbackProgress
 * */
class SimpleDownloader(context: Context, private val threshold: Long = 10 * 1024 * 1024L, private val progressInterval: Int = 150, internal val log: Boolean = false) : Downloader {
    private val splitsDir = context.splitsDir().absolutePath
    internal val downloadTasks: MutableMap<Int, Array<DownloadTask>> = mutableMapOf()
    override fun startDownload(sessionId: Int, requests: MutableList<DownloadRequest>, callback: DownloadCallback) {
        download(sessionId, requests, callback, false, false)
    }

    override fun deferredDownload(sessionId: Int, requests: MutableList<DownloadRequest>, callback: DownloadCallback, usingMobileDataPermitted: Boolean) {
        download(sessionId, requests, callback, true, !usingMobileDataPermitted)
    }

    private fun download(sessionId: Int, requests: MutableList<DownloadRequest>, callback: DownloadCallback, deferred: Boolean, wifiRequired: Boolean) {
        val tasks = requests.map { it.toTask(deferred, wifiRequired) }.toTypedArray()
        downloadTasks[sessionId] = tasks
        DownloadTask.enqueue(tasks, TaskDownloadCallBack(this, sessionId, callback))
    }

    override fun cancelDownloadSync(sessionId: Int): Boolean {
        val tasks = downloadTasks.remove(sessionId)
        if (tasks != null) DownloadTask.cancel(tasks)
        return tasks != null
    }

    override fun getDownloadSizeThresholdWhenUsingMobileData(): Long {
        return threshold
    }

    override fun isDeferredDownloadOnlyWhenUsingWifiData(): Boolean {
        return true
    }

    private fun DownloadRequest.toTask(deferred: Boolean, wifiRequired: Boolean): DownloadTask {
        val file = fileLink().tmp
        val task = DownloadTask.Builder(url, file.parent!!, file.name)
            .setConnectionCount(1)//fixError https://github.com/lingochamp/okdownload/issues/415
            .setPriority(if (deferred) 0 else 10)
            .setMinIntervalMillisCallbackProcess(progressInterval)
            .setWifiRequired(wifiRequired)
            .build()
        task.progress = DownloadProgress()
        return task
    }

    override fun calculateDownloadSize(requests: MutableList<DownloadRequest>, totalBytesToDownload: Long): Long {
        val total = requests.sumOf {
            val link = it.fileLink()
            val info = StatusUtil.getCurrentInfo(it.url, link.tmp.parent!!, link.tmp.name)
            val cacheSize = info?.totalOffset ?: if (link.real.exists()) link.real.length() else 0

            if (cacheSize >= it.size && !link.real.exists()) {//finished tmp file should rename to real one
                link.tmp.renameTo(link.real)
            }

            if (cacheSize > 0) {
                max(it.size - cacheSize, 0)
            } else it.size
        }
        return min(total, totalBytesToDownload)
    }

    /**
     *  缓存apk，MD5相同的不会重复下载，比如部分组件更新。
     * */
    private fun DownloadRequest.fileLink(): LinkFile {
        val linkFile = File(fileDir, fileName)
        if (!supportSymlink()) return LinkFile(linkFile, linkFile)
        val realFile = File("$splitsDir/$fileName", "$fileMD5.apk")
        val file = linkFile.readlink()
        if (file != null) {
            if (url.endsWith(file.name)) {
                if (log) Log.i("symlink", "ok: $linkFile => $file")
                return LinkFile(linkFile, realFile)
            } else {//link to invalid file
                val delete = linkFile.delete()
                if (log) Log.e("symlink", "invalid file: $linkFile => $file, delete:$delete")
            }
        }
        realFile.parentFile!!.mkdirs()
        linkFile.parentFile!!.mkdirs()
        return try {
            //after symlink, linkFile.exists()==realFile.exists()
            Os.symlink(realFile.absolutePath, linkFile.absolutePath)
            if (log) Log.i("symlink", "$linkFile => $realFile")
            LinkFile(linkFile, realFile)
        } catch (e: Exception) {
            if (log) Log.e("symlink", "error: $linkFile => $realFile")
            e.printStackTrace()
            LinkFile(linkFile, linkFile)
        }
    }

    private val apkReg by lazy { "\\.apk$".toRegex() }

    private inner class LinkFile(val link: File?, val real: File) {
        val tmp = File(real.absolutePath.replace(apkReg, ".tmp"))
    }
}

private fun File.readlink(): File? {
    if (supportSymlink()) {
        try {
            val path = Os.readlink(absolutePath)
            if (path != null) return File(path)
        } catch (e: Exception) {
        }
    }
    return null
}

@SuppressLint("AnnotateVersionCheck")
fun supportSymlink(): Boolean {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
}